/* C.Filter: Functions and data structures for handling command filters */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"

#include "utils.h"
#include "filter.h"

extern int _os_cmd (char *);

#define INFILE(f) (f->file[f->in])
#define OUTFILE(f) (f->file[1-f->in])

/* The linked list of open filters */
static FILTER *filter_list = NULL;

/* Create an entry in the filter list for a new filter. Allocate two file
 * names, for command input and output, and create an empty output. Open the
 * input and output file descriptors. Return the address of the new filter,
 * or NULL for errors (out of memory, cannot create temporary files, etc).
 */
FILTER *FLTopen (void)
{
	FILE *fd;
	FILTER *new = malloc(sizeof(FILTER));
	char namebuf[11];

	/* Get a new FILTER structure */
	if (new == NULL)
		return NULL;

	/* Temporary file names are based on the address of new */
	sprintf(namebuf, "A%.8x", (int)new);

	/* Record the first filename (initial input) */
	new->file[0] = mktemp(namebuf);
	if (new->file[0] == NULL)
	{
		free(new);
		return NULL;
	}

	/* Record the second filename (initial output) */
	namebuf[0] = 'B';
	new->file[1] = mktemp(namebuf);
	if (new->file[1] == NULL)
	{
		free(new->file[0]);
		free(new);
		return NULL;
	}

	/* Create an empty initial output file */
	fd = fopen(new->file[1],"w");
	if (fd == NULL)
	{
		free(new->file[0]);
		free(new->file[1]);
		free(new);
		return NULL;
	}
	fclose(fd);

	/* Set up new->fd_in to be a write file pointer where input to
	 * the filter can be written. Note that once a command has been
	 * executed, further input is simply tacked on the end of the
	 * current file.
	 */
	fd = fopen(new->file[0],"w");
	if (fd == NULL)
	{
		remove(new->file[1]);
		free(new->file[0]);
		free(new->file[1]);
		free(new);
		return NULL;
	}

	new->fd_in = fd;

	/* Set up new->fd_out to be a read file pointer where the current
	 * output from the filter can be read. Note that initially, the
	 * output is empty.
	 */
	fd = fopen(new->file[1],"r");
	if (fd == NULL)
	{
		fclose(new->fd_in);
		remove(new->file[0]);
		remove(new->file[1]);
		free(new->file[0]);
		free(new->file[1]);
		free(new);
		return NULL;
	}

	new->fd_out = fd;

	/* Initialise the last command return value (0), and the index
	 * of the current input file.
	 */
	new->retval = 0;
	new->in = 0;

	/* Link new into the current file structure */
	if (filter_list == NULL)
	{
		new->next = new->prev = new;
		filter_list = new;
	}
	else
	{
		new->next = filter_list;
		new->prev = filter_list->prev;
		filter_list = new;
	}

	return new;
}

/* Close a filter. Close and remove any temporary files, and delete all
 * storage associated with the filter. Return EOF on errors (filter is not
 * a valid filter, temporary files cannot be closed etc). Note that errors
 * encountered while closing a corrupted filter (retval FILTER_ERROR) are
 * ignored.
 */
int FLTclose (FILTER *filter)
{
	register FILTER *p;
	register int i;
	int error = 0;

	/* Make sure there is at least one open filter */
	if (filter_list == NULL)
		return EOF;

	/* Check that filter is a valid filter */
	for (p = filter_list->next; ; p = p->next)
	{
		if (p == filter)
			break;

		if (p == filter_list)
			return EOF;
	}

	/* Try to close the file pointers, remembering any errors */
	if (filter->fd_in && fclose(filter->fd_in) == EOF && filter->retval != FILTER_ERROR)
		error = EOF;

	if (fclose(filter->fd_out) == EOF && filter->retval != FILTER_ERROR)
		error = EOF;

	/* Try to delete the temporary files, remembering any errors */
	for (i = 0; i < 2; ++i)
		if (remove(filter->file[i]) && filter->retval != FILTER_ERROR)
			error = EOF;

	/* Free allocated memory for filenames */
	for (i = 0; i < 2; ++i)
		free(filter->file[i]);

	/* Unlink the filter structure from the list */
	if (filter->next == filter)
		filter_list = NULL;
	else
	{
		filter->prev->next = filter->next;
		filter->next->prev = filter->prev;
	}

	/* Free the filter structure */
	free(filter);

	return error;
}

/* Pass the current contents of a filter through a command. Record the
 * command return value, and swap the two files.
 * Return the command return value, or FILTER_ERROR for errors in running
 * the command or managing the temporary files.
 * Note that a result of FILTER_ERROR means that filter errors have occurred.
 * The only valid action on an erroneous filter is to close it!
 */
int FLTfilter (FILTER *filter, char *cmd)
{
	register int i;
	int oscmd;
	FILE *fd;
	char *tmpname;
	char cmdbuf[256];

	if (filter->retval == FILTER_ERROR)
		return FILTER_ERROR;

	if (filter->fd_in && fclose(filter->fd_in) == EOF)
	{
		filter->retval = FILTER_ERROR;
		return FILTER_ERROR;
	}

	if (fclose(filter->fd_out) == EOF)
	{
		filter->retval = FILTER_ERROR;
		return FILTER_ERROR;
	}

	if (remove(OUTFILE(filter)) == EOF)
	{
		filter->retval = FILTER_ERROR;
		return FILTER_ERROR;
	}

	/* Now we have to take the input file INFILE(filter) and
	 * cram it into the command cmd. We grab the output in file
	 * OUTFILE(filter).
	 */

	oscmd = _os_cmd(cmd);

	if (*cmd == '%')
	{
		oscmd = 1;
		++cmd;
	}

	/* Create the required command string */
	if (oscmd)
	{
		tmpname = mktemp("Filter");

		if (tmpname == NULL)
		{
			filter->retval = FILTER_ERROR;
			return FILTER_ERROR;
		}

		i = sprintf(cmdbuf, "%s{ < %s > %s }", cmd,
				INFILE(filter), tmpname);
	}
	else
	{
		register char *s;

		/* Skip initial spaces */
		while (*cmd && isspace(*cmd))
			++cmd;

		/* Find the end of the program name */
		s = cmd;
		while (*s && !isspace(*s))
			++s;
			
		i = sprintf(cmdbuf, "%.*s < %s > %s%s", s - cmd, cmd,
				INFILE(filter), OUTFILE(filter), s);
	}

	/* Check for overflow in command buffer */
	if (i > 255)
		filter->retval = FILTER_ERROR;
	else
	{
		_kernel_setenv("Sys$ReturnCode", "0");
		filter->retval = system(cmdbuf);
	}

	if (filter->retval == FILTER_ERROR)
	{
		if (oscmd)
		{
			remove(tmpname);
			free(tmpname);
		}

		return FILTER_ERROR;
	}

	/* If the command was an OS command, RISC OS has kindly added a CR
	 * to the end of each line. So we get the joy of removing it.
	 */
	if (oscmd)
	{
		int ch;
		FILE *in = fopen(tmpname, "r");
		FILE *out = fopen(OUTFILE(filter), "w");

		if (in == NULL || out == NULL)
		{
			remove(tmpname);
			free(tmpname);
			filter->retval = FILTER_ERROR;
			return FILTER_ERROR;
		}

		/* Strip out CRs from the output */
		while ((ch = getc(in)) != EOF && !ferror(out))
		{
			if (ch != '\r')
				putc(ch, out);
		}

		/* Did we succeed? */
		ch = (ferror(in) || ferror(out));

		/* Tidy up */
		fclose(in);
		fclose(out);
		remove(tmpname);
		free(tmpname);

		if (ch)
		{
			filter->retval = FILTER_ERROR;
			return FILTER_ERROR;
		}
	}

	/* We have successfully mangled the input file. We must now reopen
	 * the output file pointer, kill the input file pointer (you can
	 * no longer add input), and swap the files.
	 */

	fd = fopen(OUTFILE(filter), "r");
	if (fd == NULL)
	{
		filter->retval = FILTER_ERROR;
		return FILTER_ERROR;
	}
	filter->fd_out = fd;
	filter->fd_in = NULL;

	filter->in = 1 - filter->in;

	return filter->retval;
}

#ifdef test
int main (int argc, char *argv[])
{
	FILTER *f;
	int ch;
	char *in;
	FILE *fp;
	char cmd[BUFSIZ];

	if (argc != 2)
	{
		printf("Usage Filter file\nThen type a list of commands...\n");
		return 0;
	}

	in = argv[1];

	if ((fp = fopen(in,"r")) == NULL)
	{
		printf("Cannot open %s\n", in);
		return 1;
	}

	f = FLTopen();

	while ((ch = getc(fp)) != EOF)
		putc(ch, FLTin(f));

	while (fgets(cmd, BUFSIZ, stdin))
	{
		int len = strlen(cmd);

		if (len == 0)
			break;

		if (cmd[len-1] == '\n')
			cmd[len-1] = '\0';

		printf("========== Return value: %d\n", FLTfilter(f,cmd));

		while ((ch = getc(FLTout(f))) != EOF)
			putchar(ch);

		printf("==========\n");
	}

	if (FLTclose(f) == EOF)
		printf("========== Close error!!!\n");

	return 0;
}
#endif
